package com.yeskery.nut.application.nio;

import com.yeskery.nut.application.NutApplication;
import com.yeskery.nut.application.NutServer;
import com.yeskery.nut.application.NutServerConfigure;
import com.yeskery.nut.core.*;
import com.yeskery.nut.http.nio.NioRequest;
import com.yeskery.nut.http.nio.NioResponse;
import com.yeskery.nut.util.IOUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * 非阻塞模式下的 Nut Server 模式，基于 nio 的 ServerSocketChannel
 * 和 SocketChannel 实现.
 * @author sprout
 * @version 1.0
 * 2019-03-18 23:10
 *
 * @deprecated 推荐使用 {@link com.yeskery.nut.application.sun.SunNutServer}
 * 或 {@link com.yeskery.nut.application.netty.NettyNutServer}.
 */
@Deprecated
public class NioNutServer implements NutServer {

	/** 日志对象 */
	private static final Logger logger = Logger.getLogger(NioNutServer.class.getName());

    /** 是否持续启动服务 */
    private boolean enable = true;

    @Override
    public void startServer(NutServerConfigure nutServerConfigure) throws Exception {
		//使用NIO需要用到ServerSocketChannel
        //其中包含一个ServerSocket对象
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        //创建地址对象
        InetSocketAddress localAddress = new InetSocketAddress(nutServerConfigure.getPort());
        //服务器绑定地址
        serverChannel.bind(localAddress);
        //设置为非阻塞
        serverChannel.configureBlocking(false);
		logger.info(getServerStartedTip(nutServerConfigure.getPort(), "NIO", false));
        //注册到selector，会调用ServerSocket的accept
        //我们用selector监听accept能否返回
        //当调用accept可以返回时，会得到通知
        //注意，是可以返回，还需要调用accept
        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

		monitor(selector, nutServerConfigure.getNutApplication(), nutServerConfigure.getServerContext(),
				nutServerConfigure.getDispatcher(), nutServerConfigure.getSessionManager(),
				nutServerConfigure.getServerRequestConfiguration(), null);
    }

	@Override
    public void close() throws IOException {
        enable = false;
    }

	/**
	 * 处理连接，并将请求转发到对应的 {@link Controller} 中去
	 * @param selector Selector
	 * @param nutApplication Nut应用对象
	 * @param serverContext 服务上下文
	 * @param dispatcher 请求转发器
	 * @param sessionManager Session 管理器
	 * @param serverRequestConfiguration 服务请求配置对象
	 * @param object 用于存储运算参数
	 * @throws IOException IOException
	 */
    protected void monitor(Selector selector, NutApplication nutApplication, ServerContext serverContext, Dispatcher dispatcher,
						   SessionManager sessionManager, ServerRequestConfiguration serverRequestConfiguration, Object object) throws IOException {
		while (enable) {
			//调用select，阻塞在这里，直到有注册的channel满足条件
			if (selector.select() <= 0) {
				continue;
			}
			//如果走到这里，有符合条件的channel
			//可以通过selector.selectedKeys().iterator()拿到符合条件的迭代器
			Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator();
			//处理满足条件的keys
			while (keysIterator.hasNext()) {
				//取出一个key并移除
				SelectionKey key = keysIterator.next();
				keysIterator.remove();
				try {
					if (key.isAcceptable()) {
						doAccept(nutApplication, selector, key, object);
					} else if (key.isReadable()) {
						doRead(nutApplication, key, serverContext, dispatcher, sessionManager, serverRequestConfiguration, object);
					} else if (key.isWritable()) {
						doWrite(key, object);
					}
				} catch (Exception e) {
					logger.log(Level.WARNING, "An Error Occurred While Closing The Socket.", e);
					//当客户端Socket关闭时，会走到这里，清理资源
					key.cancel();
					try {
						key.channel().close();
					} catch (IOException e1) {
						logger.log(Level.WARNING, "An Error Occurred While Closing The Channel.", e);
					}
				}
			}
		}
	}

	/**
	 * 处理客户端连接的方法
	 * @param nutApplication Nut应用对象
	 * @param selector selector
	 * @param key key
	 * @param object 用于存储运算参数
	 * @throws IOException IOException
	 */
    protected void doAccept(NutApplication nutApplication, Selector selector, SelectionKey key, Object object) throws Exception {
		//有accept可以返回
		//取得可以操作的channel
		ServerSocketChannel socketChannel = (ServerSocketChannel) key.channel();
		//调用accept完成三次握手，返回与客户端可以通信的channel
		SocketChannel channel = socketChannel.accept();
		//将该channel置非阻塞
		channel.configureBlocking(false);
		//注册进selector，当可读或可写时将得到通知，select返回
		channel.register(selector, SelectionKey.OP_READ);
	}

	/**
	 * 处理读取数据的方法
	 * @param nutApplication Nut应用对象
	 * @param key key
	 * @param serverContext 服务上下文
	 * @param dispatcher 请求转发器
	 * @param sessionManager Session 管理器
	 * @param serverRequestConfiguration 服务请求配置对象
	 * @param object 用于存储运算参数
	 * @throws IOException IOException
	 */
	protected void doRead(NutApplication nutApplication, SelectionKey key, ServerContext serverContext, Dispatcher dispatcher,
						  SessionManager sessionManager, ServerRequestConfiguration serverRequestConfiguration, Object object) throws Exception {
		//有channel可读,取出可读的channel
		SocketChannel channel = (SocketChannel) key.channel();
		byte[] bytes = IOUtils.readHttpByteArray(channel);
		if (bytes.length == 0) {
			return;
		}
		//执行请求响应处理
		Request request = new NioRequest(channel, bytes, serverContext, sessionManager, serverRequestConfiguration);
		NioResponse response = new NioResponse(nutApplication);
		//请求执行的方法
		process(request, response, dispatcher, sessionManager);
		//附加上buffer，供写出使用
		key.attach(response.getBytes());
		key.interestOps(SelectionKey.OP_WRITE);
	}

	/**
	 * 处理数据写出的方法
	 * @param key key
	 * @param object 用于存储运算参数
	 * @throws IOException IOException
	 */
	protected void doWrite(SelectionKey key, Object object) throws Exception {
		//有channel可写,取出可写的channel
		SocketChannel channel = (SocketChannel) key.channel();
		//取出可读时设置的缓冲区
		byte[] bytes = (byte[]) key.attachment();
		//写回数据
		channel.write(ByteBuffer.wrap(bytes));
		//变为等待读
		key.interestOps(SelectionKey.OP_READ);
	}
}
